<template>
  <div class="line-chart">
    <div class="main" ref="main"></div>
    <div
      class="tooltip"
      :style="{ left: ev.left + 20 + 'px', top: ev.top + 20 + 'px' }"
      v-show="visible"
    >
      <div class="topic" v-text="ev.topic"></div>
      <div class="detail">
        <span v-text="ev.xLabel + '：'"></span>
        <span v-text="ev.xValue"></span>
      </div>
      <div class="detail">
        <span v-text="ev.yLabel + '：'"></span>
        <span v-text="ev.yValue"></span>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
import { Vue, Component, Ref } from "vue-property-decorator";
import * as d3 from "d3";
import Icon from "../TriangleChart/Icon";

type D3Selection = d3.Selection<any, any, any, any>;

@Component({
  name: "RelationLineChart",
  components: {}
})
export default class RelationLineChart extends Vue {
  /**
   * 折线关系图配置 dsdcdss
   */
  private config!: RelationLineChartConfig;

  @Ref("main")
  private $main!: HTMLDivElement;

  /**
   * svg宽度
   */
  private w!: number;
  /**
   * svg高度
   */
  private h!: number;
  /**
   * 图形偏移量，根据config.padding 确定
   */
  private offset!: number;

  /**
   * svg容器
   */
  private svg!: D3Selection;

  /**
   * def容器
   */
  private defs!: D3Selection;

  /**
   * x轴比例尺
   */
  private xScale!: d3.ScaleLinear<any, any, any>;

  /**
   * y轴比例尺
   */
  private yScale!: d3.ScaleLinear<any, any, any>;

  private visible: boolean = false;

  private ev = {
    topic: "",
    xLabel: "",
    xValue: "",
    yLabel: "",
    yValue: "",
    left: 0,
    top: 0
  };

  public setOPtions(config: RelationLineChartConfig) {
    this.config = config;
    this.start();
  }

  private start() {
    this.init();
    this.initStyle();
    this.addAxisX();
    this.addAxisY();
    this.addLines();
    this.createSymbols();
    this.addLegends();
    this.bindEvents();
  }

  bindEvents() {
    const _this = this;
    this.svg
      .selectAll(".point-item")
      .on("mousemove", function () {
        const rect = (
          this as unknown as HTMLDivElement
        ).getBoundingClientRect();
        const node = d3.select(this);
        const data = node.datum() as any;
        _this.ev.xValue = data.x;
        _this.ev.yValue = data.y;
        _this.ev.topic = node.attr("sery");
        _this.ev.left = rect.left;
        _this.ev.top = rect.top;
        _this.visible = true;
      })
      .on("mouseout", function () {
        _this.visible = false;
      });
  }

  private init() {
    this.$main.innerHTML = "";
    const rect = this.$el.getBoundingClientRect();

    this.ev.xLabel = this.config.axises.x.label || "x";
    this.ev.yLabel = this.config.axises.y.label || "y";

    this.offset = this.config.padding + 40;
    this.w = rect.width;
    this.h = rect.height;

    this.svg = d3
      .select(this.$main)
      .append("svg")
      .attr("width", rect.width)
      .attr("height", rect.height);

    this.defs = this.svg.append("defs");
  }

  private initStyle() {
    const style = this.defs.append("style");
    style.text(`
      .x-axis .axis path{stroke: ${this.config.axises.x.color || "#000"}}
      .x-axis .axis line{stroke: ${this.config.axises.x.color || "#000"}}
      ${
        this.config.axises.x.showTick === false
          ? ".x-axis .axis{display:none}"
          : ""
      }
      .x-axis text{fill: ${this.config.axises.x.color || "#000"}}
      .x-axis .label text {
        font-size: ${this.config.axises.x.fontSize || 14}px;
        font-weight: ${this.config.axises.x.fontWeight || "bold"};
      }
      .y-axis .axis path{stroke: ${this.config.axises.y.color || "#000"}}
      .y-axis .axis line{stroke: ${this.config.axises.y.color || "#000"}}
      ${
        this.config.axises.y.showTick === false
          ? ".y-axis .axis{display:none}"
          : ""
      }
      .y-axis text{fill: ${this.config.axises.y.color || "#000"}}
      .y-axis .label text {
        font-size: ${this.config.axises.y.fontSize || 14}px;
        font-weight: ${this.config.axises.y.fontWeight || "bold"};
      }
    `);
  }

  private addAxisX() {
    const maxLineX = d3.max(this.config.lines, (d) =>
      d3.max(d.points, (p) => p.x)
    ) as number;

    const maxSeryX = d3.max(this.config.series, (d) =>
      d3.max(d.data, (p) => p.x)
    ) as number;

    const maxX = d3.max([maxLineX, maxSeryX]) as number;

    this.xScale = d3
      .scaleLinear()
      .range([this.offset, this.w - this.offset])
      .domain([0, maxX])
      .nice();

    const axis = d3.axisBottom(this.xScale);

    const g = this.svg
      .append("g")
      .classed("x-axis", true)
      .attr("transform", `translate(0, ${this.h - this.offset})`);

    g.append("g").classed("axis", true).call(axis);

    if (this.config.axises.x.label) {
      g.append("g")
        .classed("label", true)
        .append("text")
        .text(this.config.axises.x.label)
        .attr("x", this.w / 2)
        .attr("y", 0)
        .attr("dy", "2em")
        .style("text-anchor", "middle")
        .style("dominant-baseline", "hanging");
    }
  }

  private addAxisY() {
    const maxLineY = d3.max(this.config.lines, (d) =>
      d3.max(d.points, (p) => p.y)
    ) as number;

    const maxSeryY = d3.max(this.config.series, (d) =>
      d3.max(d.data, (p) => p.y)
    ) as number;

    const maxY = d3.max([maxLineY, maxSeryY]) as number;

    this.yScale = d3
      .scaleLinear()
      .range([this.h - this.offset, this.offset])
      .domain([0, maxY])
      .nice();

    const axis = d3.axisLeft(this.yScale);

    const g = this.svg
      .append("g")
      .classed("y-axis", true)
      .attr("transform", `translate(${this.offset}, 0)`);
    g.append("g").classed("axis", true).call(axis);

    if (this.config.axises.y.label) {
      g.append("g")
        .classed("label", true)
        .append("text")
        .text(this.config.axises.y.label)
        .attr("x", 0)
        .attr("y", this.h / 2)
        .attr("dx", "-3em")
        .style("writing-mode", "tb")
        .style("text-anchor", "middle")
        .style("dominant-baseline", "middle");
    }
  }

  private addLines() {
    const g = this.svg
      .append("g")
      .classed("lines", true)
      .selectAll("g")
      .data(this.config.lines)
      .enter()
      .append("g")
      .classed("r-line", true);

    const d3Line = d3
      .line<{ x: number; y: number }>()
      .curve(d3.curveCardinal)
      .x((d) => this.xScale(d.x))
      .y((d) => this.yScale(d.y));

    g.append("path")
      .attr("d", (d) => (d.points.length > 0 ? d3Line(d.points) : ""))
      .attr("fill", "none")
      .attr("stroke", (d) => d.color || "#000")
      .attr("stroke-width", (d) => d.width || 2)
      .attr("stroke-dasharray", (d) => (d.type === "dash" ? "6" : ""))
      .attr("m", (d) => {
        if (d.label?.text) {
          const ps = d.points;
          const text = g.append("text").text(d.label.text);
          const position = d.label.position || "start";
          const anchor = position === "start" ? "end" : "start";
          const point = position === "start" ? ps[0] : ps[ps.length - 1];
          text
            .attr("x", this.xScale(point.x))
            .attr("y", this.yScale(point.y))
            .attr("text-anchor", anchor)
            .attr("dy", "-1em")
            .style("font-size", d.label.fontSize || 14)
            .style("font-weight", d.label.fontWeight || "bold")
            .style("fill", d.color || "#000");
        }
        return "";
      });
  }

  private createSymbols() {
    const g = this.svg.append("g").classed("pointsgroup", true);
    this.config.series.forEach((sery) => {
      const id = Icon.createIconMapping(sery.icon || {}, this.defs);
      sery.icon.id = id;
      const pg = g.append("g").classed("points", true);
      this.addPoints(sery, pg);
    });
  }

  private addPoints(sery: RelationLineChartSery, g: D3Selection) {
    const size = sery.icon?.size || Icon.defaultIconSize;
    g.selectAll("use")
      .data(sery.data)
      .enter()
      .append("use")
      .classed("point-item", true)
      .attr("xlink:href", "#" + sery.icon.id || "")
      .attr("width", size)
      .attr("height", size)
      .attr("sery", sery.name)
      .attr("x", (d) => this.xScale(d.x) - size / 2)
      .attr("y", (d) => this.yScale(d.y) - size / 2);
  }

  private addLegends() {
    const g = this.svg
      .append("g")
      .classed("legends", true)
      .attr("transform", `translate(${this.w - 20} ,0)`);
    const legends = g
      .selectAll("g")
      .data(this.config.series)
      .enter()
      .append("g")
      .classed("legend", true);

    legends
      .append("text")
      .text((d) => d.name)
      .attr("y", (d, i) => i * 20 + 30)
      .attr("x", -10)
      .style("text-anchor", "end");

    legends
      .append("use")
      .attr("xlink:href", (d) => "#" + d.icon.id + "")
      .attr("y", (d, i) => i * 20 + 30 - 12)
      .attr("x", -5)
      .attr("width", 12)
      .attr("height", 12);
  }
}
</script>
<style lang="scss" scoped>
.line-chart {
  width: 100%;
  height: 100%;
  ::v-deep {
    .point-item {
      cursor: pointer;
    }
  }
  .tooltip {
    position: fixed;
    background-color: rgba($color: #000000, $alpha: 0.6);
    padding: 10px;
    color: #fff;
    border-style: solid;
    white-space: nowrap;
    z-index: 9999999;
    box-shadow: rgba(0, 0, 0, 0.2) 1px 2px 10px;
    transition: left 0.2s cubic-bezier(0.23, 1, 0.32, 1) 0s,
      top 0.2s cubic-bezier(0.23, 1, 0.32, 1);
    background-color: rgba(0, 0, 0, 0.7);
    border-width: 1px;
    border-radius: 4px;
    font-size: 14px;
    border-color: rgb(255, 255, 255);
    pointer-events: none;
    text-align: left;
    .topic {
      font-size: 16px;
    }
    .detail {
      font-size: 14px;
      margin-top: 2px;
    }
  }
}
</style>
